package com.an.blog.strategy.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.an.blog.admin.domain.bo.BlogSocialUserAuthBo;
import com.an.blog.admin.domain.bo.BlogSocialUserBo;
import com.an.blog.admin.domain.vo.BlogSocialUserAuthVo;
import com.an.blog.admin.domain.vo.BlogSocialUserVo;
import com.an.common.properties.CallBackProperties;
import com.an.blog.service.IBlogSocialUserAuthService;
import com.an.blog.service.IBlogSocialUserService;
import com.an.blog.strategy.SocialLoginStrategy;
import com.an.blog.web.domain.BlogBindBo;
import com.an.common.constant.CacheConstants;
import com.an.common.core.domain.entity.SysUser;
import com.an.common.enums.DeviceType;
import com.an.common.exception.blog.BlogException;
import com.an.common.helper.LoginHelper;
import com.an.common.utils.StringUtils;
import com.an.common.utils.redis.RedisUtils;
import com.an.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.enums.AuthResponseStatus;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;

/**
 * gitee impl打来
 *
 * @author: Ann
 * @createDate: 2023/08/24 22:18:27
 * @version: 1.0.0
 */
@Service("GiteeStrategyImpl")
@RequiredArgsConstructor
@Slf4j
public class GiteeSocialLoginImpl implements SocialLoginStrategy {

    private final IBlogSocialUserService socialUserService;

    private final IBlogSocialUserAuthService socialUserAuthService;

    private final ISysUserService sysUserService;

    private final CallBackProperties callBackProperties;


    /**
     * GITEE社会登录回调
     *
     * @param response        响应
     * @param callback        回调
     * @param servletResponse servlet响应
     * @author: Ann
     * @createDate: 2023/09/01 11:52:58
     * @version: 1.0.0
     */
    @Override
    public void socialLoginCallBack(AuthResponse<AuthUser> response, AuthCallback callback,
                                    HttpServletResponse servletResponse) throws IOException {
        if (response.getCode() != AuthResponseStatus.SUCCESS.getCode()) {
            throw new BlogException("blog.web.social.login.fail", response.getMsg());
        }
        String callBackBaseUrl = callBackProperties.getBaseUrl();
        Boolean cookieSecure = callBackProperties.getCookieSecure();
        //第三方用户信息
        AuthUser authUser = response.getData();
        AuthToken authToken = authUser.getToken();
        //code
        String accessToken = authToken.getAccessToken();
        //1.先查询是否有第三方平台用户信息
        String source = authUser.getSource();
        String uuid = authUser.getUuid();
        //根据source和uuid确认平台账号信息
        BlogSocialUserVo blogSocialUserVo = socialUserService.queryOneBySourceAndUuid(source, uuid);

        long socialUserId = 0;
        //如果没有这个用户就新增，如果有的话，就更新
        if (ObjectUtil.isNull(blogSocialUserVo)) {
            BlogSocialUserBo blogSocialUserBo = buildSocialUserBo(authToken, accessToken, source, uuid);
            Boolean flag = socialUserService.insertByBo(blogSocialUserBo);
            if (flag) {
                socialUserId = blogSocialUserBo.getId();
            }

        } else if (!blogSocialUserVo.getAccessToken().equals(accessToken)) {
            BlogSocialUserBo blogSocialUserBo = buildSocialUserBo(authToken, accessToken, source, uuid);
            blogSocialUserBo.setId(blogSocialUserVo.getId());
            socialUserService.updateByBo(blogSocialUserBo);
            socialUserId = blogSocialUserVo.getId();
        }
        //查询是否绑定了已有用户
        BlogSocialUserAuthVo blogSocialUserAuthVo = socialUserAuthService.queryVoBySocialUserId(socialUserId);
        if (ObjectUtil.isNull(blogSocialUserAuthVo)) {
            String state = callback.getState();
            String code = callback.getCode();
            //将回调信息存储在Redis,跳转后，拿着相关信息从redis获取判断是否过期，防止在页面太久。
            // 如果是注册并绑定，则拿着Redis的社交用户信息存储到数据库中。
            String redisKey = StringUtils.format(CacheConstants.SOCIAL_LOGIN_KEY, source) + code + "_" + state;
            RedisUtils.setCacheObject(redisKey, authUser, Duration.ofMinutes(10));
            //说明没有绑定账号，跳转到绑定账号界面
            servletResponse.sendRedirect(callBackBaseUrl + "/auth/gitee/callback/?code=" + code + "&state=" + state);
        } else {
            //绑定了账号，直接登录
            SysUser sysUser = sysUserService.selectUserById(blogSocialUserAuthVo.getUserId());
            LoginHelper.login4Blog(sysUser, DeviceType.BLOG);
            String token = StpUtil.getTokenValue();
            try {
                Cookie cookie = new Cookie("Blog-Token", token);
                cookie.setPath("/");
                cookie.setSecure(cookieSecure);
                servletResponse.addCookie(cookie);
            } catch (Exception e) {
                log.error("cookie回填", e);
            }
            servletResponse.sendRedirect(callBackBaseUrl);
        }
    }


    /**
     * 构建社会用户业务对象
     *
     * @param authToken   身份验证令牌
     * @param accessToken 访问令牌
     * @param source      源
     * @param uuid        uuid
     * @return {@link BlogSocialUserBo }
     * @author: Ann
     * @createDate: 2023/08/25 20:30:35
     * @version: 1.0.0
     */
    private static BlogSocialUserBo buildSocialUserBo(AuthToken authToken, String accessToken, String source, String uuid) {
        BlogSocialUserBo blogSocialUserBo = new BlogSocialUserBo();
        blogSocialUserBo.setUuid(uuid);
        blogSocialUserBo.setSource(source);
        blogSocialUserBo.setAccessToken(accessToken);
        blogSocialUserBo.setExpireIn(authToken.getExpireIn());
        blogSocialUserBo.setRefreshToken(authToken.getRefreshToken());
        blogSocialUserBo.setTokenType(authToken.getTokenType());
        blogSocialUserBo.setScope(authToken.getScope());
        return blogSocialUserBo;
    }

    /**
     * @param blogBindBo 博客绑定参数
     * @param sysUser       系统用户
     * @author: Ann
     * @createDate: 2023/09/01 14:23:50
     * @version: 1.0.0
     */
    @Override
    public void loginBindSocialUser(BlogBindBo blogBindBo, SysUser sysUser) {
        bindMethod(blogBindBo, sysUser);
    }

    /**
     * @param blogBindBo 博客绑定参数
     * @param sysUser       系统用户
     * @return {@link AuthUser }
     * @author: Ann
     * @createDate: 2023/09/01 14:33:37
     * @version: 1.0.0
     */
    private AuthUser bindMethod(BlogBindBo blogBindBo, SysUser sysUser) {
        String source = blogBindBo.getSource();
        String state = blogBindBo.getState();
        String socialCode = blogBindBo.getSocialCode();
        String redisKey = StringUtils.format(CacheConstants.SOCIAL_LOGIN_KEY, source) + socialCode + "_" + state;
        AuthUser authUser = RedisUtils.getCacheObject(redisKey);
        if (ObjectUtil.isNull(authUser)) {
            throw new BlogException("blog.web.social.login.fail", "页面已失效，请重新登录");
        }
        BlogSocialUserBo blogSocialUserBo = new BlogSocialUserBo();
        blogSocialUserBo.setUuid(authUser.getUuid());
        blogSocialUserBo.setAccessToken(authUser.getToken().getAccessToken());
        blogSocialUserBo.setSource(source);
        BlogSocialUserVo blogSocialUserVo = socialUserService.queryOne(blogSocialUserBo);
        if (ObjectUtil.isNull(blogSocialUserVo)) {
            throw new BlogException("blog.web.social.login.fail", "错误的请求");
        }
        BlogSocialUserAuthBo blogSocialUserAuthBo = new BlogSocialUserAuthBo();
        blogSocialUserAuthBo.setSocialUserId(blogSocialUserVo.getId());
        blogSocialUserAuthBo.setUserId(sysUser.getUserId());
        socialUserAuthService.insertByBo(blogSocialUserAuthBo);
        return authUser;
    }

    /**
     * @param blogBindBo 博客绑定参数
     * @param sysUser       系统用户
     * @return {@link AuthUser }
     * @author: Ann
     * @createDate: 2023/09/01 14:28:11
     * @version: 1.0.0
     */
    @Override
    public AuthUser registerBindSocialUser(BlogBindBo blogBindBo, SysUser sysUser) {
        return bindMethod(blogBindBo, sysUser);
    }
}
